package sc.tools;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import sc.math.SimplexNoise;
import sc.server.Server;
import sc.server.world.Tile;

public final class MapTool extends JFrame implements ActionListener
{

	/** The version of the serialised form of this class. */
	private static final long serialVersionUID = 1L;

	/** Constants */
	private static final int WIDTH = 1000;
	private static final int DEPTH = 1000;
	private static final int WATERLEVEL = 10;
	private static final int WATERBORDER = 100;
	private static final int LANDRISE = 5;

	/** Display elements */
	private static JLabel canvasLabel;
	private static BufferedImage map;
	private static int[] pixels;
	private static JLabel treeCountValue;

	/** Map statistics */
	private static int tree_count;

	/**
	 * Creates an instance of MapMaker.
	 * 
	 * @param width
	 * @param height
	 * @throws SQLException
	 * @throws ClassNotFoundException
	 */
	public MapTool(int width, int height) throws ClassNotFoundException,
			SQLException
	{
		setTitle("Map Maker");
		setSize(width, height);
		layoutWindow();
		setVisible(true);
	}

	/**
	 * Lays out the window controls.
	 */
	private void layoutWindow()
	{
		JButton btn;

		// Add the controls panel to the left hand side
		JPanel panel = new JPanel(new BorderLayout());
		getContentPane().add(panel, BorderLayout.WEST);

		// Add a reload map button to the top of the panel
		btn = new JButton("Reload");
		btn.addActionListener(this);
		panel.add(btn, BorderLayout.NORTH);

		// Add a save button to the top of the panel
		btn = new JButton("Save");
		btn.addActionListener(this);
		panel.add(btn, BorderLayout.SOUTH);

		// Add a info panel
		JPanel infopanel = new JPanel(new GridLayout(7, 1));
		panel.add(infopanel, BorderLayout.CENTER);

		// Add number of trees labels
		JLabel treeCountLabel = new JLabel("Trees:");
		treeCountValue = new JLabel("0");
		JPanel treeCountPanel = new JPanel();
		treeCountPanel.add(treeCountLabel);
		treeCountPanel.add(treeCountValue);
		infopanel.add(treeCountPanel);

		map = new BufferedImage(WIDTH, DEPTH, BufferedImage.TYPE_INT_RGB);
		canvasLabel = new JLabel(new ImageIcon(map));
		canvasLabel.setHorizontalAlignment(JLabel.CENTER);
		JScrollPane sp = new JScrollPane(canvasLabel);
		getContentPane().add(sp, BorderLayout.CENTER);

		addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
				System.exit(0);
			}
		});

		// Get a direct direct DataBuffer onto the bufferedimage
		WritableRaster raster = map.getRaster();
		pixels = ((DataBufferInt) raster.getDataBuffer()).getData();
	}

	/**
	 * (Re)generates the world map.
	 */
	public void regenerate()
	{
		double combined;
		int height;

		// Set random seeds
		int octave1_seed = (int) (Math.random() * 5000);
		int octave2_seed = (int) (Math.random() * 5000);
		int trees_seed = (int) (Math.random() * 5000);

		// Zero statistics
		tree_count = 0;

		for (int y = 0; y < DEPTH; y++)
		{
			for (int x = 0; x < WIDTH; x++)
			{

				/** There are multiple layers of noise that make up the terrain */

				// Layer 0: Defines a tapered edge so water frames the map
				double taper = 0;
				if (y < WATERBORDER)
				{
					taper = ((double) (WATERBORDER - y) / WATERBORDER);
				} else if (y > DEPTH - WATERBORDER)
				{
					taper = ((double) (y - DEPTH + WATERBORDER) / WATERBORDER);
				}
				if (x < WATERBORDER)
				{
					taper += ((double) (WATERBORDER - x) / WATERBORDER);
				} else if (x > WIDTH - WATERBORDER)
				{
					taper += ((double) (x - WIDTH + WATERBORDER) / WATERBORDER);
				}
				double octave0 = ((LANDRISE + WATERLEVEL - 128) / 128f)
						- (taper * taper);

				// Layer 1: Defines the large scale water and land forms
				double octave1 = SimplexNoise.noise(x * 0.005, y * 0.005,
						octave1_seed);

				// Layer 2: Defines smaller scale deformations
				double octave2 = SimplexNoise.noise(x * 0.02, y * 0.02,
						octave2_seed) * 0.2;

				// Combine the layers together
				combined = octave0 + octave1 + octave2;

				// Bound noise to the range [-1, 1]
				if (combined > 1)
					combined -= (combined - 1);
				else if (combined < -1)
					combined = -1;

				// Convert combined noise to height
				// [-1, 1] => [0, 256]
				height = (int) Math.round(combined * 128) + 128;

				// Set the green colour component of the pixel to represent
				// height
				int rgb = height << 8;

				if (height < WATERLEVEL)
				{
					// DEBUG: For testing set pixel to blue if below WATERLEVEL
					rgb += 75;
				} else
				{
					// We are above WATERLEVEL so apply trees
					if (Math.random() > SimplexNoise.noise(x * 0.005,
							y * 0.005, trees_seed) + 0.8)
					{
						rgb += 128;
						tree_count++;
					}
				}

				pixels[y * WIDTH + x] = rgb;
			}
		}

		// Update map info
		canvasLabel.repaint();
		treeCountValue.setText(String.valueOf(tree_count));
	}

	/**
	 * Saves the current world map to disk.
	 */
	public void save()
	{
		int x = 0;
		int z = 0;

		// Make a Tile out of each pixel in the image
		for (int i = 0; i < pixels.length; i++)
		{
			int height = (pixels[i] & 0x0000FF00) >> 8;
			new Tile((x - Tile.HALFMAPSIZE) * Tile.WIDTH, height,
					(z - Tile.HALFMAPSIZE) * Tile.WIDTH);

			x++;
			if (x == WIDTH)
			{
				x = 0;
				z++;
			}
		}
	}

	@Override
	public void actionPerformed(ActionEvent e)
	{
		if (e.getActionCommand() == "Reload")
		{
			regenerate();
		} else if (e.getActionCommand() == "Save")
		{
			save();
		}
	}

	/**
	 * Program entry point.
	 * 
	 * @param args
	 */
	public static void main(String[] args)
	{
		try
		{
			MapTool mm = new MapTool(1024, 738);
			mm.regenerate();
		}
		catch (Exception e)
		{
			Server.logger.severe(e.getMessage());
		}
	}
}
